<template>
  <div class="w-full h-dvh max-h-dvh overflow-hidden" :style="{ backgroundColor: coverRgb }">
    <div class="w-screen h-screen absolute inset-0 pointer-events-none" style="background: linear-gradient(180deg, rgba(0, 0, 0, 0) 0%, rgba(38, 38, 38, 1) 80%)"></div>
    <div class="absolute inset-0 w-screen h-dvh flex items-center justify-center z-10">
      <div class="w-full p-2 sm:p-4 md:p-8">
        <div v-if="!isMobileLandscape" :style="{ width: coverWidth + 'px', height: coverHeight + 'px' }" class="mx-auto overflow-hidden rounded-xl my-2">
          <img ref="coverImg" :src="coverUrl" class="object-contain w-full h-full" @load="coverImageLoaded" />
        </div>
        <p class="text-2xl lg:text-3xl font-semibold text-center mb-1 line-clamp-2">{{ mediaItemShare.playbackSession.displayTitle || 'No title' }}</p>
        <p v-if="mediaItemShare.playbackSession.displayAuthor" class="text-lg lg:text-xl text-slate-400 font-semibold text-center mb-1 truncate">{{ mediaItemShare.playbackSession.displayAuthor }}</p>

        <div class="w-full pt-16">
          <player-ui ref="audioPlayer" :chapters="chapters" :current-chapter="currentChapter" :paused="isPaused" :loading="!hasLoaded" :is-podcast="false" hide-bookmarks hide-sleep-timer @playPause="playPause" @jumpForward="jumpForward" @jumpBackward="jumpBackward" @setVolume="setVolume" @setPlaybackRate="setPlaybackRate" @seek="seek" />
        </div>

        <ui-tooltip v-if="mediaItemShare.isDownloadable" direction="bottom" :text="$strings.LabelDownload" class="absolute top-0 left-0 m-4">
          <button aria-label="Download" class="text-gray-300 hover:text-white" @click="downloadShareItem"><span class="material-symbols text-2xl sm:text-3xl">download</span></button>
        </ui-tooltip>
      </div>
    </div>
  </div>
</template>

<script>
import LocalAudioPlayer from '../../players/LocalAudioPlayer'
import { FastAverageColor } from 'fast-average-color'

export default {
  layout: 'blank',
  async asyncData({ params, error, app, query }) {
    let endpoint = `/public/share/${params.slug}`
    if (query.t && !isNaN(query.t)) {
      endpoint += `?t=${query.t}`
    }
    const mediaItemShare = await app.$axios.$get(endpoint, { timeout: 10000 }).catch((error) => {
      console.error('Failed', error)
      return null
    })
    if (!mediaItemShare) {
      return error({ statusCode: 404, message: 'Media item not found or expired' })
    }

    return {
      mediaItemShare: mediaItemShare
    }
  },
  data() {
    return {
      localAudioPlayer: new LocalAudioPlayer(),
      playerState: null,
      playInterval: null,
      hasLoaded: false,
      totalDuration: 0,
      windowWidth: 0,
      windowHeight: 0,
      listeningTimeSinceSync: 0,
      coverRgb: null,
      coverBgIsLight: false,
      currentTime: 0
    }
  },
  computed: {
    playbackSession() {
      return this.mediaItemShare.playbackSession
    },
    coverUrl() {
      if (!this.playbackSession.coverPath) return `${this.$config.routerBasePath}/book_placeholder.jpg`
      return `${this.$config.routerBasePath}/public/share/${this.mediaItemShare.slug}/cover`
    },
    downloadUrl() {
      return `${process.env.serverUrl}/public/share/${this.mediaItemShare.slug}/download`
    },
    audioTracks() {
      return (this.playbackSession.audioTracks || []).map((track) => {
        track.relativeContentUrl = track.contentUrl
        return track
      })
    },
    isPlaying() {
      return this.playerState === 'PLAYING'
    },
    isPaused() {
      return !this.isPlaying
    },
    chapters() {
      return this.playbackSession.chapters || []
    },
    currentChapter() {
      return this.chapters.find((chapter) => chapter.start <= this.currentTime && this.currentTime < chapter.end)
    },
    coverAspectRatio() {
      const coverAspectRatio = this.playbackSession.coverAspectRatio
      return coverAspectRatio === this.$constants.BookCoverAspectRatio.STANDARD ? 1.6 : 1
    },
    isMobileLandscape() {
      return this.windowWidth > this.windowHeight && this.windowHeight < 450
    },
    coverWidth() {
      const availableCoverWidth = Math.min(450, this.windowWidth - 32)
      const availableCoverHeight = Math.min(450, this.windowHeight - 250)

      const mostCoverHeight = availableCoverWidth * this.coverAspectRatio
      if (mostCoverHeight > availableCoverHeight) {
        return availableCoverHeight / this.coverAspectRatio
      }
      return availableCoverWidth
    },
    coverHeight() {
      return this.coverWidth * this.coverAspectRatio
    }
  },
  methods: {
    mediaSessionPlay() {
      console.log('Media session play')
      this.play()
    },
    mediaSessionPause() {
      console.log('Media session pause')
      this.pause()
    },
    mediaSessionStop() {
      console.log('Media session stop')
      this.pause()
    },
    mediaSessionSeekBackward() {
      console.log('Media session seek backward')
      this.jumpBackward()
    },
    mediaSessionSeekForward() {
      console.log('Media session seek forward')
      this.jumpForward()
    },
    mediaSessionSeekTo(e) {
      console.log('Media session seek to', e)
      if (e.seekTime !== null && !isNaN(e.seekTime)) {
        this.seek(e.seekTime)
      }
    },
    mediaSessionPreviousTrack() {
      if (this.$refs.audioPlayer) {
        this.$refs.audioPlayer.prevChapter()
      }
    },
    mediaSessionNextTrack() {
      if (this.$refs.audioPlayer) {
        this.$refs.audioPlayer.nextChapter()
      }
    },
    updateMediaSessionPlaybackState() {
      if ('mediaSession' in navigator) {
        navigator.mediaSession.playbackState = this.isPlaying ? 'playing' : 'paused'
      }
    },
    setMediaSession() {
      // https://developer.mozilla.org/en-US/docs/Web/API/Media_Session_API
      if ('mediaSession' in navigator) {
        const chapterInfo = []
        if (this.chapters.length > 0) {
          this.chapters.forEach((chapter) => {
            chapterInfo.push({
              title: chapter.title,
              startTime: chapter.start
            })
          })
        }

        navigator.mediaSession.metadata = new MediaMetadata({
          title: this.mediaItemShare.playbackSession.displayTitle || 'No title',
          artist: this.mediaItemShare.playbackSession.displayAuthor || 'Unknown',
          artwork: [
            {
              src: this.coverUrl
            }
          ],
          chapterInfo
        })
        console.log('Set media session metadata', navigator.mediaSession.metadata)

        navigator.mediaSession.setActionHandler('play', this.mediaSessionPlay)
        navigator.mediaSession.setActionHandler('pause', this.mediaSessionPause)
        navigator.mediaSession.setActionHandler('stop', this.mediaSessionStop)
        navigator.mediaSession.setActionHandler('seekbackward', this.mediaSessionSeekBackward)
        navigator.mediaSession.setActionHandler('seekforward', this.mediaSessionSeekForward)
        navigator.mediaSession.setActionHandler('seekto', this.mediaSessionSeekTo)
        navigator.mediaSession.setActionHandler('previoustrack', this.mediaSessionSeekBackward)
        navigator.mediaSession.setActionHandler('nexttrack', this.mediaSessionSeekForward)
      } else {
        console.warn('Media session not available')
      }
    },
    async coverImageLoaded(e) {
      if (!this.playbackSession.coverPath) return
      const fac = new FastAverageColor()
      fac
        .getColorAsync(e.target)
        .then((color) => {
          this.coverRgb = color.rgba
          this.coverBgIsLight = color.isLight

          document.body.style.backgroundColor = color.hex
        })
        .catch((e) => {
          console.log(e)
        })
    },
    playPause() {
      if (this.isPlaying) {
        this.pause()
      } else {
        this.play()
      }
    },
    play() {
      if (!this.localAudioPlayer || !this.hasLoaded) return
      this.localAudioPlayer.play()
    },
    pause() {
      if (!this.localAudioPlayer || !this.hasLoaded) return
      this.localAudioPlayer.pause()
    },
    jumpForward() {
      if (!this.localAudioPlayer || !this.hasLoaded) return
      const currentTime = this.localAudioPlayer.getCurrentTime()
      const duration = this.localAudioPlayer.getDuration()
      const jumpForwardAmount = this.$store.getters['user/getUserSetting']('jumpForwardAmount') || 10
      this.seek(Math.min(currentTime + jumpForwardAmount, duration))
    },
    jumpBackward() {
      if (!this.localAudioPlayer || !this.hasLoaded) return
      const currentTime = this.localAudioPlayer.getCurrentTime()
      const jumpBackwardAmount = this.$store.getters['user/getUserSetting']('jumpBackwardAmount') || 10
      this.seek(Math.max(currentTime - jumpBackwardAmount, 0))
    },
    setVolume(volume) {
      if (!this.localAudioPlayer || !this.hasLoaded) return
      this.localAudioPlayer.setVolume(volume)
    },
    setPlaybackRate(playbackRate) {
      if (!this.localAudioPlayer || !this.hasLoaded) return
      this.localAudioPlayer.setPlaybackRate(playbackRate)
    },
    seek(time) {
      if (!this.localAudioPlayer || !this.hasLoaded) return

      this.localAudioPlayer.seek(time, this.isPlaying)
      this.setCurrentTime(time)
    },
    setCurrentTime(time) {
      if (!this.$refs.audioPlayer) return

      // Update UI
      this.$refs.audioPlayer.setCurrentTime(time)
      this.currentTime = time
    },
    setDuration() {
      if (!this.localAudioPlayer) return
      this.totalDuration = this.localAudioPlayer.getDuration()
      if (this.$refs.audioPlayer) {
        this.$refs.audioPlayer.setDuration(this.totalDuration)
      }
    },
    sendProgressSync(currentTime) {
      console.log('Sending progress sync for time', currentTime)
      const progress = {
        currentTime
      }
      this.$axios.$patch(`/public/share/${this.mediaItemShare.slug}/progress`, progress, { progress: false }).catch((error) => {
        console.error('Failed to send progress sync', error)
      })
    },
    startPlayInterval() {
      let lastTick = Date.now()
      clearInterval(this.playInterval)
      this.playInterval = setInterval(() => {
        if (!this.localAudioPlayer) return

        const currentTime = this.localAudioPlayer.getCurrentTime()
        this.setCurrentTime(currentTime)
        const exactTimeElapsed = (Date.now() - lastTick) / 1000
        lastTick = Date.now()
        this.listeningTimeSinceSync += exactTimeElapsed
        if (this.listeningTimeSinceSync >= 30) {
          this.listeningTimeSinceSync = 0
          this.sendProgressSync(currentTime)
        }
      }, 1000)
    },
    stopPlayInterval() {
      clearInterval(this.playInterval)
      this.playInterval = null
    },
    playerStateChange(state) {
      this.playerState = state
      if (state === 'LOADED' || state === 'PLAYING') {
        this.setDuration()
      }
      if (state === 'LOADED') {
        this.hasLoaded = true
      }
      if (state === 'PLAYING') {
        this.startPlayInterval()
      } else {
        this.stopPlayInterval()
      }
      this.updateMediaSessionPlaybackState()
    },
    playerTimeUpdate(time) {
      this.setCurrentTime(time)
    },
    getHotkeyName(e) {
      var keyCode = e.keyCode || e.which
      if (!this.$keynames[keyCode]) {
        // Unused hotkey
        return null
      }

      var keyName = this.$keynames[keyCode]
      var name = keyName
      if (e.shiftKey) name = 'Shift-' + keyName
      if (process.env.NODE_ENV !== 'production') {
        console.log('Hotkey command', name)
      }
      return name
    },
    keyDown(e) {
      if (!this.localAudioPlayer || !this.hasLoaded) return

      var name = this.getHotkeyName(e)
      if (!name) return

      // Playing audiobook
      if (Object.values(this.$hotkeys.AudioPlayer).includes(name)) {
        this.$eventBus.$emit('player-hotkey', name)
        e.preventDefault()
      }
    },
    resize() {
      this.windowWidth = window.innerWidth
      this.windowHeight = window.innerHeight
    },
    playerError(error) {
      console.error('Player error', error)
      this.$toast.error('Failed to play audio on device')
    },
    playerFinished() {
      console.log('Player finished')
    },
    downloadShareItem() {
      this.$downloadFile(this.downloadUrl)
    }
  },
  mounted() {
    this.$store.dispatch('user/loadUserSettings')

    this.resize()
    window.addEventListener('resize', this.resize)
    window.addEventListener('keydown', this.keyDown)

    if (process.env.NODE_ENV === 'development') {
      console.log('Loaded media item share', this.mediaItemShare)
    }

    const startTime = this.playbackSession.currentTime || 0
    this.localAudioPlayer.set(null, this.audioTracks, false, startTime, false)
    this.localAudioPlayer.on('stateChange', this.playerStateChange.bind(this))
    this.localAudioPlayer.on('timeupdate', this.playerTimeUpdate.bind(this))
    this.localAudioPlayer.on('error', this.playerError.bind(this))
    this.localAudioPlayer.on('finished', this.playerFinished.bind(this))

    this.setMediaSession()
  },
  beforeDestroy() {
    window.removeEventListener('resize', this.resize)
    window.removeEventListener('keydown', this.keyDown)

    this.localAudioPlayer.off('stateChange', this.playerStateChange.bind(this))
    this.localAudioPlayer.off('timeupdate', this.playerTimeUpdate.bind(this))
    this.localAudioPlayer.off('error', this.playerError.bind(this))
    this.localAudioPlayer.off('finished', this.playerFinished.bind(this))
    this.localAudioPlayer.destroy()
  }
}
</script>
